﻿
package extremefx.conditions {
	import extremefx.IDisposable;
	import extremefx.IEnumerable;
	import extremefx.IEnumerator;
	import extremefx.collections.ICollection;
	import extremefx.collections.IDictionary;
	import extremefx.collections.IList;		

	/**
	 * @author Marcelo Volmaro
	 */
	public final class CollectionValidator extends Validator {

		public function CollectionValidator(pValue:IEnumerable, pArgumentName:String, pAdditionalMessage:String = null) {
			super(pValue, pArgumentName, pAdditionalMessage);
		}
		
		public function isEmpty(pDescription:String = null):CollectionValidator {
			if (_isSequenceNullOrEmpty(_value)) return this;
			
			throw _buildException(
				_getFormattedConditionMessage(
					Resources.m_empty, 
					pDescription, 
					_argumentName
				));
		}
		
		public function isNotEmpty(pDescription:String = null):CollectionValidator {
			if (!_isSequenceNullOrEmpty(_value)) return this;
			
			throw _buildException(
				_getFormattedConditionMessage(
					Resources.m_nEmpty, 
					pDescription, 
					_argumentName
				));
		}
		
		public function contains(pValue:*, pDescription:String = null):CollectionValidator {
			if (_contains(_value, pValue)) return this;
			
			throw _buildException(
				_getFormattedConditionMessage(
					Resources.m_contA, 
					pDescription, 
					_argumentName,
					pValue
				));
		}
		
		public function doesNotContains(pValue:*, pDescription:String = null):CollectionValidator {
			if (!_contains(_value, pValue)) return this;
			
			throw _buildException(
				_getFormattedConditionMessage(
					Resources.m_nContA, 
					pDescription, 
					_argumentName,
					pValue
				));
		}

		public function containsAny(pValues:IEnumerable, pDescription:String = null):CollectionValidator {
			if (_containsAny(_value, pValues)) return this;
			
			throw _buildException(
				_getFormattedConditionMessage(
					Resources.m_atLeast, 
					pDescription, 
					_argumentName,
					pValues
				));
		}
		
		public function doesNotContainsAny(pValues:IEnumerable, pDescription:String = null):CollectionValidator {
			if (!_containsAny(_value, pValues)) return this;
			
			throw _buildException(
				_getFormattedConditionMessage(
					Resources.m_notAny, 
					pDescription, 
					_argumentName,
					pValues
				));
		}
		
		public function containsAll(pValues:IEnumerable, pDescription:String = null):CollectionValidator {
			if (_containsAll(_value, pValues)) return this;
			
			throw _buildException(
				_getFormattedConditionMessage(
					Resources.m_contAll, 
					pDescription, 
					_argumentName,
					pValues
				));
		}
		
		public function doesNotContainsAll(pValues:IEnumerable, pDescription:String = null):CollectionValidator {
			if (!_containsAll(_value, pValues)) return this;
			
			throw _buildException(
				_getFormattedConditionMessage(
					Resources.m_nContAll, 
					pDescription, 
					_argumentName,
					pValues
				));
		}
		
		public function hasLength(pNumElements:uint, pDescription:String = null):CollectionValidator {
			if (_sequenceHasLength(_value, pNumElements, _equals, _moreThan, false)) return this;
			
			throw _buildException(
				_getFormattedConditionMessage(
					Resources.m_contX, 
					pDescription, 
					_argumentName,
					pNumElements
				));	
		}
		
		public function doesNotHasLength(pNumElements:uint, pDescription:String = null):CollectionValidator {
			if (!_sequenceHasLength(_value, pNumElements, _equals, _moreThan, false)) return this;
			
			throw _buildException(
				_getFormattedConditionMessage(
					Resources.m_nContX, 
					pDescription, 
					_argumentName,
					pNumElements
				));	
		}
		
		public function isShorterThan(pNumElements:uint, pDescription:String = null):CollectionValidator {
			if (_sequenceHasLength(_value, pNumElements, _lessThan, _moreEqual, false)) return this;
			
			throw _buildException(
				_getFormattedConditionMessage(
					Resources.m_ncmtX, 
					pDescription, 
					_argumentName,
					pNumElements
				));	
		}
		
		public function isNotShorterThan(pNumElements:uint, pDescription:String = null):CollectionValidator {
			if (!_sequenceHasLength(_value, pNumElements, _lessThan, _moreEqual, false)) return this;
			
			throw _buildException(
				_getFormattedConditionMessage(
					Resources.m_ncltX, 
					pDescription, 
					_argumentName,
					pNumElements
				));	
		}
		
		public function isShorterOrEqual(pNumElements:uint, pDescription:String = null):CollectionValidator {
			if (_sequenceHasLength(_value, pNumElements, _lessEqual, _moreThan, false)) return this;
			
			throw _buildException(
				_getFormattedConditionMessage(
					Resources.m_scXole, 
					pDescription, 
					_argumentName,
					pNumElements
				));	
		}
		
		public function isNotShorterOrEqual(pNumElements:uint, pDescription:String = null):CollectionValidator {
			if (!_sequenceHasLength(_value, pNumElements, _lessEqual, _moreThan, false)) return this;
			
			throw _buildException(
				_getFormattedConditionMessage(
					Resources.m_sncXole, 
					pDescription, 
					_argumentName,
					pNumElements
				));	
		}
		
		public function isLongerThan(pNumElements:uint, pDescription:String = null):CollectionValidator {
			if (_sequenceHasLength(_value, pNumElements, _moreThan, _moreThan, true)) return this;
			
			throw _buildException(
				_getFormattedConditionMessage(
					Resources.m_scmtXe, 
					pDescription, 
					_argumentName,
					pNumElements
				));	
		}
		
		public function isNotLongerThan(pNumElements:uint, pDescription:String = null):CollectionValidator {
			if (!_sequenceHasLength(_value, pNumElements, _moreThan, _moreThan, true)) return this;
			
			throw _buildException(
				_getFormattedConditionMessage(
					Resources.m_sncmtXe, 
					pDescription, 
					_argumentName,
					pNumElements
				));	
		}
		
		public function isLongerOrEqual(pNumElements:uint, pDescription:String = null):CollectionValidator {
			if (_sequenceHasLength(_value, pNumElements, _moreEqual, _moreEqual, true)) return this;
			
			throw _buildException(
				_getFormattedConditionMessage(
					Resources.m_scXome, 
					pDescription, 
					_argumentName,
					pNumElements
				));	
		}
		
		public function isNotLongerOrEqual(pNumElements:uint, pDescription:String = null):CollectionValidator {
			if (!_sequenceHasLength(_value, pNumElements, _moreEqual, _moreEqual, true)) return this;
			
			throw _buildException(
				_getFormattedConditionMessage(
					Resources.m_sncXome, 
					pDescription, 
					_argumentName,
					pNumElements
				));	
		}
		
		private function _containsAll(pValues:IEnumerable, pEnumerable:IEnumerable):Boolean {
			if (_isSequenceNullOrEmpty(pValues)) return true;
			if (_isSequenceNullOrEmpty(pEnumerable)) return false;
			
			var e:IEnumerator = pValues.getEnumerator();
			while (e.moveNext()){
				if (!_contains(e.current, pEnumerable)) return false;
			}
			
			return true;
		}
		
		private function _containsAny(pValues:IEnumerable, pEnumerable:IEnumerable):Boolean {
			if (_isSequenceNullOrEmpty(pEnumerable) || _isSequenceNullOrEmpty(pValues)) return false;
			
			var e:IEnumerator = pValues.getEnumerator();
			while (e.moveNext()){
				if (_contains(e.current, pEnumerable)) return true;
			}
			
			return false;
		}
		
		private function _contains(pValue:*, pEnumerable:IEnumerable):Boolean {
			if (_isSequenceNullOrEmpty(pEnumerable)) return false;
			
			if (pEnumerable is IDictionary) return IDictionary(pEnumerable).contains(pValue);
			if (pEnumerable is IList) return IList(pEnumerable).contains(pValue);
			
			var e:IEnumerator = pEnumerable.getEnumerator();
			while (e.moveNext()){
				if (pValue == e.current) return true;
			}
			
			return false;
		}
		
		private function _sequenceHasLength(pEnumerable:IEnumerable, pLen:uint, pComparator:Function, pOptim:Function, pRet:Boolean):Boolean {
			if (pEnumerable == null) return pComparator(0, pLen);
			
			if (pEnumerable is ICollection) return pComparator(ICollection(pEnumerable).count, pLen);
			
			var e:IEnumerator = pEnumerable.getEnumerator();
			var lengthOfSequence:uint = 0;
			
			try {
				while (e.moveNext()){
					++lengthOfSequence;
					if (pOptim(lengthOfSequence, pLen)) return pRet;
				}
				
				return pComparator(lengthOfSequence, pLen);
				
			} finally {
				if (e is IDisposable){
					IDisposable(e).dispose();
				}
			}
		}
		
		private function _equals(pA:uint, pB:uint):Boolean {
			return pA == pB;
		}
		
		private function _lessThan(pA:uint, pB:uint):Boolean {
			return pA < pB;
		}
		
		private function _lessEqual(pA:uint, pB:uint):Boolean {
			return pA <= pB;
		}
		
		private function _moreThan(pA:uint, pB:uint):Boolean {
			return pA > pB;
		}
		
		private function _moreEqual(pA:uint, pB:uint):Boolean {
			return pA >= pB;
		}
		
		private function _isSequenceNullOrEmpty(pValue:IEnumerable):Boolean {
			if (pValue == null) return true;
			if (pValue is ICollection) return ICollection(pValue).count != 0;
			
			var e:IEnumerator = pValue.getEnumerator();
			while (e.moveNext()){
				return false;
			}
			
			return true;
		}
	}
}
